package com.example.aspect;

import cn.hutool.core.util.StrUtil;
import com.example.api.exception.CommonRuntimeException;
import common.annotations.Inner;
import lombok.SneakyThrows;
import org.aspectj.lang.annotation.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @Author Story
 * @Date 2022/3/22 21:52
 * @Version 1.0 接口调用情况监控 1、监控单个接口一天内的调用次数 2、如果抛出异常，则记录异常信息及发生时间 3、对单个IP进行限流，每天对每个接口的调用次数有限
 */
//AS派克特
@Aspect
@Component
public class ApiCallAdvice {

    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private static final String FORMAT_PATTERN_DAY = "yyyy-MM-dd";
    private static final String FORMAT_PATTERN_MILLS = "yyyy-MM-dd HH:mm:ss:SSS";
    //破in泰忒
    @Pointcut("@annotation(com.example.annotations.ApiCallAdvice)")
    @Inner
    public void costPointCut() {
    }

    /**
     * 前置aop切入的类 真正执行业务操作前先进行限流的验证 限制维度为：一天内单个IP的访问次数 key = URI + IP + date（精确到天） value = 调用次数
     */
    @SneakyThrows
    //Before 标识一个前置增强方法
    @Before("costPointCut()")
    @Inner
    public void before() {
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //获取请求的request
        HttpServletRequest request = attributes.getRequest();

        String uri = request.getRequestURI();
        String date = dateFormat(FORMAT_PATTERN_DAY);
        String ip = getRequestIp(request);

        if (StrUtil.isEmpty(ip)) {
            throw new CommonRuntimeException("IP不能为空");
        }
        // URI+IP+日期 构成以天为维度的key
        String ipKey = uri + "_" + ip + "_" + date;
        if (redisTemplate.hasKey(ipKey)) {
            if (Integer.parseInt(redisTemplate.opsForValue().get(ipKey).toString()) > 200) {
                throw new CommonRuntimeException("访问失败，已超过访问次数。");
            }
            redisTemplate.opsForValue().increment(ipKey, 1);
        } else {
            stringRedisTemplate.opsForValue().set(ipKey, "1", 1L, TimeUnit.DAYS);
        }
    }

    /**
     * 如果有返回结果，代表一次调用，则对应接口的调用次数加一，统计维度为天 （Redis使用Hash结构） key = URI key = date （精确到天） value = 调用次数
     */
    // AfterReturning 后置增强，方法正常退出时执行
    @AfterReturning("costPointCut()")
    @Inner
    public void afterReturning() {
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //获取请求的request
        HttpServletRequest request = attributes.getRequest();

        String uri = request.getRequestURI();
        String date = dateFormat(FORMAT_PATTERN_DAY);
        /**
         * key存在====调用次数+1
         */
        if (redisTemplate.hasKey(uri)) {
            redisTemplate.boundHashOps(uri).increment(date, 1);
        } else {
            redisTemplate.boundHashOps(uri).put(date, 1);
        }
    }

    /**
     * 如果调用抛出异常，则缓存异常信息（Redis使用Hash结构） key = URI + “_exception” key = time (精确到毫秒的时间) value = exception 异常信息
     *
     * @param ex 异常信息
     */
    //AfterThrowing 异常抛出增强，方法执行抛出异常后执行
    @AfterThrowing(value = "costPointCut()", throwing = "ex")
    @Inner
    public void afterThrowing(Exception ex) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        String uri = request.getRequestURI() + "_exception";
        String time = dateFormat(FORMAT_PATTERN_MILLS);
        String exception = ex.getMessage();

        redisTemplate.boundHashOps(uri).put(time, exception);
    }

    /**
     * 获取请求信息
     *
     * @param request
     * @return
     */
    private String getRequestIp(HttpServletRequest request) {
        // 获取请求IP
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip) || "null".equals(ip)) {
            ip = "" + request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip) || "null".equals(ip)) {
            ip = "" + request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip) || "null".equals(ip)) {
            ip = "" + request.getRemoteAddr();
        }
        return ip;
    }

    private String dateFormat(String pattern) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
        return dateFormat.format(new Date());
    }
}
